using Microsoft.Extensions.Logging;
using SumerCoreDevOps.ExecuteNodeApi.Services;
using SumerCoreDevOps.Infrastructure.Tcp;
using SumerCoreDevOps.Shared.Enums;
using SumerCoreDevOps.Shared.Models.Messages;
using System.Formats.Tar;
using System.IO.Compression;
using System.Security.Cryptography;
using System.Text.Json;

namespace SumerCoreDevOps.ExecuteNodeApi.Handlers;

/// <summary>
/// 构建产物打包处理器
/// </summary>
public class ArchiveArtifactsHandler : IStepHandler
{
    private readonly ILogger<ArchiveArtifactsHandler> _logger;
    private readonly TaskExecutor _taskExecutor;
    private readonly TcpMultiMasterClient _tcpClient;
    private readonly IConfiguration _configuration;
    private readonly NodeConfigPersistenceService _nodeConfigService;

    public ArchiveArtifactsHandler(
        ILogger<ArchiveArtifactsHandler> logger,
        TaskExecutor taskExecutor,
        TcpMultiMasterClient tcpClient,
        IConfiguration configuration,
        NodeConfigPersistenceService nodeConfigService)
    {
        _logger = logger;
        _taskExecutor = taskExecutor;
        _tcpClient = tcpClient;
        _configuration = configuration;
        _nodeConfigService = nodeConfigService;
    }

    public async Task<StepExecutionResult> ExecuteAsync(WorkflowStepDispatchMessage message)
    {
        try
        {
            var config = JsonSerializer.Deserialize<ArchiveArtifactsConfig>(message.StepConfig);

            if (config == null || config.SourcePaths == null || config.SourcePaths.Count == 0)
            {
                return new StepExecutionResult
                {
                    Success = false,
                    ErrorMessage = "打包配置无效：必须指定至少一个 SourcePath"
                };
            }

            await SendLog(message, "开始打包构建产物...");

            // 确定打包格式
            var format = config.Format?.ToLower() ?? "zip";
            if (format != "zip" && format != "tar.gz")
            {
                format = "zip";
            }

            // 生成产物文件名
            var timestamp = DateTime.Now.ToString("yyyyMMdd-HHmmss");
            var artifactName = string.IsNullOrEmpty(config.ArtifactName)
                ? $"build-artifact-{message.TaskId}-{timestamp}.{format}"
                : $"{config.ArtifactName}.{format}";

            // 获取工作目录
            var workDirectory = message.Context.TryGetValue("WORK_DIRECTORY", out var workDir)
                ? workDir
                : _configuration.GetValue<string>("Node:WorkDirectory") ?? "/tmp/sumer-devops";

            // 创建临时打包目录
            var artifactsDir = Path.Combine(workDirectory, "artifacts");
            Directory.CreateDirectory(artifactsDir);

            var archivePath = Path.Combine(artifactsDir, artifactName);

            await SendLog(message, $"打包格式: {format}");
            await SendLog(message, $"输出文件: {archivePath}");

            // 验证源路径
            var validPaths = new List<string>();
            foreach (var sourcePath in config.SourcePaths)
            {
                var fullPath = Path.IsPathRooted(sourcePath)
                    ? sourcePath
                    : Path.Combine(workDirectory, sourcePath);

                if (Directory.Exists(fullPath) || File.Exists(fullPath))
                {
                    validPaths.Add(fullPath);
                    await SendLog(message, $"✓ 发现: {sourcePath}");
                }
                else
                {
                    await SendLog(message, $"⚠ 未找到: {sourcePath}", "Warning");
                }
            }

            if (validPaths.Count == 0)
            {
                return new StepExecutionResult
                {
                    Success = false,
                    ErrorMessage = "没有找到任何有效的源文件或目录"
                };
            }

            // 执行打包
            long fileSize;
            string md5Hash;

            if (format == "zip")
            {
                fileSize = await CreateZipArchiveAsync(validPaths, archivePath, config.ExcludePatterns);
            }
            else // tar.gz
            {
                fileSize = await CreateTarGzArchiveAsync(validPaths, archivePath, config.ExcludePatterns);
            }

            await SendLog(message, $"打包完成，文件大小: {FormatFileSize(fileSize)}");

            // 计算 MD5
            await SendLog(message, "计算 MD5 哈希值...");
            md5Hash = await CalculateMd5Async(archivePath);
            await SendLog(message, $"MD5: {md5Hash}");

            // 上传产物信息到控制面板
            await SendLog(message, "上传产物信息到控制面板...");

            // 从 Context 中获取 BuildHistoryId（如果有）
            long buildHistoryId = 0;
            if (message.Context.TryGetValue("BUILD_HISTORY_ID", out var buildHistoryIdStr))
            {
                long.TryParse(buildHistoryIdStr, out buildHistoryId);
            }

            var uploadResult = await UploadArtifactInfoAsync(
                message.TaskId,
                buildHistoryId,
                artifactName,
                format,
                fileSize,
                archivePath,
                md5Hash,
                config.Description);

            if (!uploadResult)
            {
                await SendLog(message, "产物信息上传失败", "Warning");
            }
            else
            {
                await SendLog(message, "✅ 产物信息已上传");
            }

            // 可选：设置过期时间（自动清理）
            var expiresAt = config.RetentionDays.HasValue
                ? DateTime.UtcNow.AddDays(config.RetentionDays.Value)
                : (DateTime?)null;

            if (expiresAt.HasValue)
            {
                await SendLog(message, $"产物将在 {config.RetentionDays} 天后过期");
            }

            return new StepExecutionResult
            {
                Success = true,
                Progress = 100,
                OutputContext = new Dictionary<string, string>
                {
                    ["ARTIFACT_NAME"] = artifactName,
                    ["ARTIFACT_PATH"] = archivePath,
                    ["ARTIFACT_SIZE"] = fileSize.ToString(),
                    ["ARTIFACT_MD5"] = md5Hash,
                    ["ARTIFACT_FORMAT"] = format
                }
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "构建产物打包处理器执行失败");
            return new StepExecutionResult
            {
                Success = false,
                ErrorMessage = $"打包失败: {ex.Message}"
            };
        }
    }

    private async Task<long> CreateZipArchiveAsync(List<string> sourcePaths, string outputPath, List<string>? excludePatterns)
    {
        using var zipArchive = ZipFile.Open(outputPath, ZipArchiveMode.Create);

        foreach (var sourcePath in sourcePaths)
        {
            if (File.Exists(sourcePath))
            {
                // 单个文件
                var fileName = Path.GetFileName(sourcePath);
                if (!ShouldExclude(fileName, excludePatterns))
                {
                    zipArchive.CreateEntryFromFile(sourcePath, fileName, CompressionLevel.Optimal);
                }
            }
            else if (Directory.Exists(sourcePath))
            {
                // 目录
                AddDirectoryToZip(zipArchive, sourcePath, "", excludePatterns);
            }
        }

        var fileInfo = new FileInfo(outputPath);
        return fileInfo.Length;
    }

    private void AddDirectoryToZip(ZipArchive archive, string sourceDir, string entryPrefix, List<string>? excludePatterns)
    {
        foreach (var file in Directory.GetFiles(sourceDir))
        {
            var fileName = Path.GetFileName(file);
            if (!ShouldExclude(fileName, excludePatterns))
            {
                var entryName = Path.Combine(entryPrefix, fileName).Replace("\\", "/");
                archive.CreateEntryFromFile(file, entryName, CompressionLevel.Optimal);
            }
        }

        foreach (var dir in Directory.GetDirectories(sourceDir))
        {
            var dirName = Path.GetFileName(dir);
            if (!ShouldExclude(dirName, excludePatterns))
            {
                var newPrefix = Path.Combine(entryPrefix, dirName).Replace("\\", "/");
                AddDirectoryToZip(archive, dir, newPrefix, excludePatterns);
            }
        }
    }

    private async Task<long> CreateTarGzArchiveAsync(List<string> sourcePaths, string outputPath, List<string>? excludePatterns)
    {
        var tarPath = outputPath.Replace(".tar.gz", ".tar");

        // 创建 tar 文件
        await using (var tarStream = File.Create(tarPath))
        await using (var tarWriter = new TarWriter(tarStream))
        {
            foreach (var sourcePath in sourcePaths)
            {
                if (File.Exists(sourcePath))
                {
                    var fileName = Path.GetFileName(sourcePath);
                    if (!ShouldExclude(fileName, excludePatterns))
                    {
                        await tarWriter.WriteEntryAsync(sourcePath, fileName);
                    }
                }
                else if (Directory.Exists(sourcePath))
                {
                    await AddDirectoryToTarAsync(tarWriter, sourcePath, "", excludePatterns);
                }
            }
        }

        // 压缩为 gzip
        await using (var originalFileStream = File.OpenRead(tarPath))
        await using (var compressedFileStream = File.Create(outputPath))
        await using (var gzipStream = new GZipStream(compressedFileStream, CompressionMode.Compress))
        {
            await originalFileStream.CopyToAsync(gzipStream);
        }

        // 删除临时 tar 文件
        File.Delete(tarPath);

        var fileInfo = new FileInfo(outputPath);
        return fileInfo.Length;
    }

    private async Task AddDirectoryToTarAsync(TarWriter tarWriter, string sourceDir, string entryPrefix, List<string>? excludePatterns)
    {
        foreach (var file in Directory.GetFiles(sourceDir))
        {
            var fileName = Path.GetFileName(file);
            if (!ShouldExclude(fileName, excludePatterns))
            {
                var entryName = Path.Combine(entryPrefix, fileName).Replace("\\", "/");
                await tarWriter.WriteEntryAsync(file, entryName);
            }
        }

        foreach (var dir in Directory.GetDirectories(sourceDir))
        {
            var dirName = Path.GetFileName(dir);
            if (!ShouldExclude(dirName, excludePatterns))
            {
                var newPrefix = Path.Combine(entryPrefix, dirName).Replace("\\", "/");
                await AddDirectoryToTarAsync(tarWriter, dir, newPrefix, excludePatterns);
            }
        }
    }

    private bool ShouldExclude(string path, List<string>? excludePatterns)
    {
        if (excludePatterns == null || excludePatterns.Count == 0)
            return false;

        foreach (var pattern in excludePatterns)
        {
            // 简单的通配符匹配
            if (pattern.Contains('*'))
            {
                var regexPattern = "^" + System.Text.RegularExpressions.Regex.Escape(pattern)
                    .Replace("\\*", ".*")
                    .Replace("\\?", ".") + "$";
                if (System.Text.RegularExpressions.Regex.IsMatch(path, regexPattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase))
                    return true;
            }
            else if (path.Equals(pattern, StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }
        }

        return false;
    }

    private async Task<string> CalculateMd5Async(string filePath)
    {
        using var md5 = MD5.Create();
        await using var stream = File.OpenRead(filePath);
        var hash = await md5.ComputeHashAsync(stream);
        return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
    }

    private string FormatFileSize(long bytes)
    {
        string[] sizes = { "B", "KB", "MB", "GB", "TB" };
        double len = bytes;
        int order = 0;
        while (len >= 1024 && order < sizes.Length - 1)
        {
            order++;
            len = len / 1024;
        }
        return $"{len:0.##} {sizes[order]}";
    }

    private async Task<bool> UploadArtifactInfoAsync(
        long taskId,
        long buildHistoryId,
        string name,
        string type,
        long fileSize,
        string filePath,
        string md5Hash,
        string? description)
    {
        try
        {
            // 获取当前节点ID
            var nodeConfig = await _nodeConfigService.LoadConfigAsync();
            if (nodeConfig == null || nodeConfig.NodeId == 0)
            {
                _logger.LogError("无法获取节点ID，产物信息上传失败");
                return false;
            }

            var artifactInfo = new ArtifactUploadMessage
            {
                NodeId = nodeConfig.NodeId,
                TaskId = taskId,
                BuildHistoryId = buildHistoryId,
                Name = name,
                Type = type,
                FileSize = fileSize,
                FilePath = filePath,
                Md5Hash = md5Hash,
                Description = description
            };

            await _tcpClient.SendToAllAsync(TcpMessageType.ArtifactUpload, artifactInfo);
            return true;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "上传产物信息失败");
            return false;
        }
    }

    private async Task SendLog(WorkflowStepDispatchMessage message, string logMessage, string level = "Info")
    {
        _logger.LogInformation("[Task {TaskId}] {Message}", message.TaskId, logMessage);
        await _taskExecutor.SendLogAsync(message.TaskId, message.StepId, logMessage, level);
    }
}

/// <summary>
/// 构建产物打包配置
/// </summary>
public class ArchiveArtifactsConfig
{
    /// <summary>
    /// 源文件或目录路径列表（相对于工作目录或绝对路径）
    /// </summary>
    public List<string> SourcePaths { get; set; } = new();

    /// <summary>
    /// 产物名称（不包含扩展名）
    /// </summary>
    public string? ArtifactName { get; set; }

    /// <summary>
    /// 打包格式（zip 或 tar.gz，默认 zip）
    /// </summary>
    public string? Format { get; set; }

    /// <summary>
    /// 排除模式（支持通配符，例如：["*.log", "*.tmp", "node_modules"]）
    /// </summary>
    public List<string>? ExcludePatterns { get; set; }

    /// <summary>
    /// 描述
    /// </summary>
    public string? Description { get; set; }

    /// <summary>
    /// 保留天数（可选，用于自动清理）
    /// </summary>
    public int? RetentionDays { get; set; }
}
